package cz.uvn.orthopedy.client;

import java.util.Date;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.HighlightEvent;
import com.google.gwt.event.logical.shared.HighlightHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.*;
import com.google.gwt.user.datepicker.client.DatePicker;

import cz.uvn.orthopedy.client.entity.Doctor;
import cz.uvn.orthopedy.client.entity.Hall;
import cz.uvn.orthopedy.client.entity.Insurance;
import cz.uvn.orthopedy.client.entity.JournalEntry;
import cz.uvn.orthopedy.client.entity.Patient;
import cz.uvn.orthopedy.client.entity.Schedule;
import cz.uvn.orthopedy.client.entity.ScheduleEntry;
import cz.uvn.orthopedy.client.ui.UIUtils;
import cz.uvn.orthopedy.client.ui.controller.DoctorsController;
import cz.uvn.orthopedy.client.ui.controller.HallsController;
import cz.uvn.orthopedy.client.ui.controller.InsurancesController;
import cz.uvn.orthopedy.client.ui.controller.JournalController;
import cz.uvn.orthopedy.client.ui.controller.PanelController;
import cz.uvn.orthopedy.client.ui.controller.PatientsController;
import cz.uvn.orthopedy.client.ui.controller.ScheduleController;

public final class ApplicationController implements ClickHandler {

	private static enum State {Journal, Schedule, Halls, Doctors, Patients, Insurances}

    private JournalServiceAsync journalService;

    private LoginManager loginManager;

	private State state;
	private StackPanel menuPanel;
	private Panel contentPanel;

	private PanelController journalController;
	private PanelController scheduleController;
	private PanelController hallsController;
	private PanelController doctorsController;
	private PanelController patientsController;
	private PanelController insurancesController;
	
	private InlineHyperlink scheduleHyperLink;
	private InlineHyperlink hallsHyperLink;
	private InlineHyperlink doctorsHyperLink;
	private InlineHyperlink patientsHyperLink;
	private InlineHyperlink insurancesHyperLink;
	
	private DatePicker datePicker;
//	private Calendar calendar;
	
	private boolean hallsCallFinished;
	private boolean doctorsCallFinished;
	private boolean patientsCallFinished;
	private boolean insurancesCallFinished;
	private boolean entriesCallFinished;
	private boolean scheduleCallFinished;
	
	private Hall[] hallsCache;
	private Doctor[] doctorsCache;
	private Patient[] patientsCache; // TODO get only limited amount of patients
	private Insurance[] insurancesCache;
	private JournalEntry[] entriesCache;
	private ScheduleEntry[] scheduleCache;

	ApplicationController(LoginInfo loginInfo) {
		journalService = GWT.create(JournalService.class);
        loginManager = new LoginManager(loginInfo);
		initUI();
	}
	
//	public static synchronized ApplicationController getInstance() {
//		if (instance == null) {
//			instance = new ApplicationController();
//		}
//		return instance;
//	}
	
	private void initUI() {
		initDatePicker();
//		initCalendar();
		initMenuPanel();
		initContentPanel();
		
		journalController = new JournalController(this);
		scheduleController = new ScheduleController(this);
		hallsController = new HallsController(this);
		doctorsController = new DoctorsController(this);
		patientsController = new PatientsController(this);
		insurancesController = new InsurancesController(this);
	}
	
	private void initDatePicker() {
		datePicker = new DatePicker();
		datePicker.addHighlightHandler(new HighlightHandler<Date>() {
			@Override
			public void onHighlight(HighlightEvent<Date> event) {
				showJournal(false);
			}
		});
		datePicker.setValue(new Date(), false);
//		datePicker.setSize("5cm", "4cm");
	}
	
//	private void initCalendar() {
//		calendar = new Calendar();
//		calendar.setDate(new Date());
//		calendar.setView(CalendarViews.DAY, 1);
//		calendar.setTitle("Operation Journal for Hall #1"); // TODO
//		calendar.scrollToHour(6);
//	}
	
	@SuppressWarnings("deprecation")
	private void initMenuPanel() {
		menuPanel = new DecoratedStackPanel();
		menuPanel.setWidth("200px");

		DecoratorPanel datePickerDecorator = new DecoratorPanel();
		datePickerDecorator.add(datePicker);
		menuPanel.add(datePickerDecorator, "Journal");

		scheduleHyperLink = new InlineHyperlink("Schedule", false, "schedule");
		scheduleHyperLink.addClickHandler(this);
		menuPanel.add(scheduleHyperLink, "Schedule");

		hallsHyperLink = new InlineHyperlink("Halls", false, "halls");
		hallsHyperLink.addClickHandler(this);
		menuPanel.add(hallsHyperLink, "Halls");

		doctorsHyperLink = new InlineHyperlink("Doctors", false, "doctors");
		doctorsHyperLink.addClickHandler(this);
		menuPanel.add(doctorsHyperLink, "Doctors");

		patientsHyperLink = new InlineHyperlink("Patients", false, "patients");
		patientsHyperLink.addClickHandler(this);
		menuPanel.add(patientsHyperLink, "Patients");

		insurancesHyperLink = new InlineHyperlink("Insurances", false, "insurances");
		insurancesHyperLink.addClickHandler(this);
		menuPanel.add(insurancesHyperLink, "Insurances");
	}
	
	private void initContentPanel() {
		contentPanel = new VerticalPanel();
	}

	Panel getMainPanel() {
		return contentPanel;
	}

	Panel getMenuPanel() {
		return menuPanel;
	}

	Panel getHeaderPanel() {
		return loginManager.getHeaderPanel();
	}

	public DatePicker getDatePicker() {
		return datePicker;
	}

//	public Calendar getCalendar() {
//		return calendar;
//	}

	public JournalServiceAsync getJournalService() {
		return journalService;
	}

	// TODO implement history
	public void showJournal(boolean resetState) {
		if (resetState) {
			state = null;
		}
		if (state == State.Journal) {
			return;
		}
		contentPanel.clear();
		contentPanel.add(journalController.getView());
		state = State.Journal;

		updateDBData();
	}
	
	public void updateDBData() {
		insurancesCallFinished = false;
		doctorsCallFinished = false;
		hallsCallFinished = false;
		patientsCallFinished = false;
		entriesCallFinished = false;
		scheduleCallFinished = false;

		loadInsurances();
		loadDoctors();
		loadHalls();
		loadPatients();
		loadJournalEntries(datePicker.getValue());
		loadSchedule();
	}

	private void _showJournal() {
		// execute after all async calls been finished
		if (!hallsCallFinished ||
				!doctorsCallFinished ||
				!entriesCallFinished ||
				!insurancesCallFinished ||
				!patientsCallFinished ||
				!scheduleCallFinished) {
			return;
		}
		journalController.refresh();
	}

	public void showSchedule() {
		if (state == State.Schedule) {
			return;
		}
		contentPanel.clear();
		contentPanel.add(scheduleController.getView());
		scheduleController.refresh();
		state = State.Schedule;
	}
	
	public void showHalls() {
		if (state == State.Halls) {
			return;
		}
		contentPanel.clear();
		contentPanel.add(hallsController.getView());
		hallsController.refresh();
		state = State.Halls;
	}
	
	public void showDoctors() {
		if (state == State.Doctors) {
			return;
		}
		contentPanel.clear();
		contentPanel.add(doctorsController.getView());
		doctorsController.refresh();
		state = State.Doctors;
	}
	
	public void showPatients() {
		if (state == State.Patients) {
			return;
		}
		contentPanel.clear();
		contentPanel.add(patientsController.getView());
		patientsController.refresh();
		state = State.Patients;
	}

	public void showInsurances() {
		if (state == State.Insurances) {
			return;
		}
		contentPanel.clear();
		contentPanel.add(insurancesController.getView());
		insurancesController.refresh();
		state = State.Insurances;
	}

	@Override
	public void onClick(ClickEvent event) {
		Object source = event.getSource();
		if (source == null) {
			showJournal(false);
		} else if (source == scheduleHyperLink) {
			showSchedule();
			updateDBData();
		} else if (source == hallsHyperLink) {
			showHalls();
			updateDBData();
		} else if (source == doctorsHyperLink) {
			showDoctors();
			updateDBData();
		} else if (source == patientsHyperLink) {
			showPatients();
			updateDBData();
		} else if (source == insurancesHyperLink) {
			showInsurances();
			updateDBData();
		}
	}
	
	// =====================================
	// =========== Miscelaneous ============ 
	// =====================================
	
	public String dayToString(int day) {
		switch (day) {
		case 0:
			return "Sunday";
		case 1:
			return "Monday";
		case 2:
			return "Tuesday";
		case 3:
			return "Wednesday";
		case 4:
			return "Thursday";
		case 5:
			return "Friday";
		case 6:
			return "Saturday";
		default:
			return "<unknown day>";
		}
	}
	
	// =====================================
	// ============== Doctors ============== 
	// =====================================
	
	public void loadDoctors() {
		journalService.getDoctors(new AsyncCallback<Doctor[]>() {
			@Override
			public void onSuccess(Doctor[] doctors) {
				doctorsCache = doctors;
				doctorsCallFinished = true;
				if (state == State.Journal) {
					_showJournal();
				}
			}
			@Override
			public void onFailure(Throwable error) {
				UIUtils.handleError(error);
			}
		});
	}
	
	public Doctor[] getDoctorsCache() {
		return doctorsCache;
	}

	public Doctor getDoctorByID(Long doctorID) {
		for (Doctor doctor : doctorsCache) {
			if (doctor.getId().equals(doctorID)) {
				return doctor;
			}
		}
		return null;
	}

	// =====================================
	// =============== Halls =============== 
	// =====================================
	
	public void loadHalls() {
		journalService.getHalls(new AsyncCallback<Hall[]>() {
			@Override
			public void onSuccess(final Hall[] halls) {
				hallsCache = halls;
				hallsCallFinished = true;
				if (state == State.Journal) {
					_showJournal();
				}
			}
			@Override
			public void onFailure(Throwable error) {
				UIUtils.handleError(error);
			}
		});
	}
	
	public Hall getHallByID(Long hallID) {
		for (Hall hall : hallsCache) {
			if (hall.getId().equals(hallID)) {
				return hall;
			}
		}
		return null;
	}
	
	public Hall[] getHallsCache() {
		return hallsCache;
	}

	// =====================================
	// ============ Insurances ============= 
	// =====================================

	public void loadInsurances() {
		journalService.getInsurances(new AsyncCallback<Insurance[]>() {
			@Override
			public void onSuccess(final Insurance[] insurances) {
				insurancesCache = insurances;
				insurancesCallFinished = true;
				if (state == State.Journal) {
					_showJournal();
				}
			}
			@Override
			public void onFailure(Throwable error) {
				UIUtils.handleError(error);
			}
		});
	}
	
	public Insurance getInsuranceByID(Long insuranceID) {
		for (Insurance insurance : insurancesCache) {
			if (insurance.getId().equals(insuranceID)) {
				return insurance;
			}
		}
		return null;
	}
	
	public Insurance[] getInsurancesCache() {
		return insurancesCache;
	}

	// =====================================
	// ============= Patients ============== 
	// =====================================
	
	public void loadPatients() {
		journalService.getPatients(new AsyncCallback<Patient[]>() {
			@Override
			public void onSuccess(Patient[] patients) {
				patientsCache = patients;
				patientsCallFinished = true;
				if (state == State.Journal) {
					_showJournal();
				}
			}
			@Override
			public void onFailure(Throwable error) {
				UIUtils.handleError(error);
			}
		});
	}
	
	public Patient[] getPatientsCache() {
		return patientsCache;
	}

	public Patient getPatientByID(Long patientID) {
		for (Patient patient : patientsCache) {
			if (patient.getId().equals(patientID)) {
				return patient;
			}
		}
		return null;
	}

	// =====================================
	// ============= Entries =============== 
	// =====================================

	public void loadJournalEntries(Date date) {
		journalService.getEntries(date, new AsyncCallback<JournalEntry[]>() {
			@Override
			public void onSuccess(final JournalEntry[] entries) {
				entriesCache = entries;
				entriesCallFinished = true;
				if (state == State.Journal) {
					_showJournal();
				}
			}
			@Override
			public void onFailure(Throwable error) {
				UIUtils.handleError(error);
			}
		});
	}

	public JournalEntry[] getEntries() {
		return entriesCache;
	}

	public void saveEntry(final JournalEntry entry, final Patient patient) {
		// Save patient 
		if (patient.getId() == null) {
			journalService.createPatient(patient, new AsyncCallback<Long>() {
				@Override
				public void onSuccess(Long patientID) {
					entry.setPatientID(patientID);
					// Save entry afterwards
					_saveEntry(entry);
				}
				@Override
				public void onFailure(Throwable error) {
					UIUtils.handleError(error);
				}
			});
		} else {
			journalService.editPatient(patient, new AsyncCallback<Void>() {
				@Override
				public void onSuccess(Void ignore) {
					// Save entry afterwards
					_saveEntry(entry);
				}
				@Override
				public void onFailure(Throwable error) {
					UIUtils.handleError(error);
				}
			});
		}

	}
	
	private void _saveEntry(final JournalEntry entry) {
		if (entry.getId() == null) {
			journalService.createEntry(entry, new AsyncCallback<Void>() {
				@Override
				public void onSuccess(Void ignore) {
					showJournal(true);
				}
				@Override
				public void onFailure(Throwable error) {
					UIUtils.handleError(error);
				}
			});
		} else {
			journalService.editEntry(entry, new AsyncCallback<Void>() {
				@Override
				public void onSuccess(Void ignore) {
					showJournal(true);
				}
				@Override
				public void onFailure(Throwable error) {
					UIUtils.handleError(error);
				}
			});
		}
	}

	// =====================================
	// ============= Schedule ============== 
	// =====================================

	public void loadSchedule() {
		journalService.getScheduleEntries(new AsyncCallback<ScheduleEntry[]>() {
			@Override
			public void onSuccess(final ScheduleEntry[] schedule) {
				scheduleCache = schedule;
				scheduleCallFinished = true;
				if (state == State.Journal) {
					_showJournal();
				}
			}
			@Override
			public void onFailure(Throwable error) {
				UIUtils.handleError(error);
			}
		});
	}

	public ScheduleEntry[] getScheduleEntries() {
		return scheduleCache;
	}

	public Schedule getSchedule() {
		return new Schedule(this);
	}

	public void init(ScheduleEntry[] entries) {
		journalService.initSchedule(entries, new AsyncCallback<Void>() {
			@Override
			public void onSuccess(Void result) {
				UIUtils.message("Schedule has been created successfully");
			}
			@Override
			public void onFailure(Throwable error) {
				UIUtils.handleError(error);
			}
		});
	}
}
